﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OOO.Scene
{
	public delegate float ScaleFunc(float progress);
	
	public delegate T LerpFunc<T>(T start, T end, float progress);

	public enum TweenState
	{
 		Running,
		Paused,
		Stopped,
	}

	public abstract class Tween<T> where T : struct
	{
		private LerpFunc<T> _lerpFunc;
		private ScaleFunc _scaleFunc;

		public float CurrentTime { get; private set; }
		public float Duration { get; private set; }
		public TweenState State { get; private set; }

		public T StartValue { get; private set; }
		public T EndValue { get; private set; }
		public T CurrentValue { get; private set; }

		public Tween(LerpFunc<T> lerpFunc)
		{
			_lerpFunc = lerpFunc;
			State = TweenState.Stopped;
		}

		public void Start(T start, T end, float duration, ScaleFunc scaleFunc)
		{
			if (duration <= 0)
				throw new ArgumentException("duration must be greater than 0");

			if (scaleFunc == null)
				throw new ArgumentException("scaleFunc is null");

			CurrentTime = 0;
			StartValue = start;
			EndValue = end;
			State = TweenState.Running;
			Duration = duration;
			_scaleFunc = scaleFunc;

			UpdateValue();
		}

		public void Pause()
		{
			if (State == TweenState.Running)
			{
				State = TweenState.Paused;
			}
		}

		public void Stop()
		{
			if (State != TweenState.Stopped)
			{
				State = TweenState.Stopped;

				CurrentTime = Duration;
				UpdateValue();
			}
		}

		public void Update(float elapsedTime)
		{
			if (State != TweenState.Running)
				return;

			CurrentTime += elapsedTime;
			if (CurrentTime >= Duration)
			{
				Stop();
			}
			else
			{
				UpdateValue();
			}
		}

		private void UpdateValue()
		{
			CurrentValue = _lerpFunc(StartValue, EndValue, _scaleFunc(CurrentTime / Duration));
		}
	}

	public class FloatTween : Tween<float>
	{
		private static float LerpFloat(float start, float end, float progress)
		{
			return start + (end - start) * progress;
		}

		public FloatTween()
			: base(LerpFloat)
		{ }
	}

	public static class ScaleFuncs
	{
		public static float Linear(float progress)
		{
			return progress;
		}

		public static float QuadraticEaseIn(float progress)
		{
			return EaseInPower(progress, 2);
		}

		public static float QuadraticEaseOut(float progress)
		{
			return EaseOutPower(progress, 2);
		}

		private static float EaseInPower(float progress, int power)
		{
			return (float)Math.Pow(progress, power);
		}

		private static float EaseOutPower(float progress, int power)
		{
			int sign = power % 2 == 0 ? -1 : 1;
			return (float)(sign * (Math.Pow(progress - 1, power) + sign));
		}
	}
}
